﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace _07_ArithmeticalExpression
{
    class Program
    {
        static void Main(string[] args)
        {
            string expression = "pow(2, 3.14) * (3 - (3 * sqrt(2) - 3.2) + 1.5*0.3) "; //"(3+5.3) * 2.7 - ln(22) / pow(2.2, -1.7)";
            expression = expression.Replace(" ", String.Empty);
            string logPattern = @"(?<log>ln\((?<op1>\d+\.?\d*)\))";
            string sqrtPattern = @"(?<sqrt>sqrt\((?<op1>\d+\.?\d*)\))";
            string powPattern = @"(?<pow>pow\((?<op1>-?\d+\.?\d*),(?<op2>-?\d+\.\d*)\))";

            while (expression.Contains('l') || expression.Contains('s') || expression.Contains('p'))
            {
                expression = Regex.Replace(expression, logPattern, delegate(Match match)//Find,calculate and replace ln(decimal)
                {
                    string value = match.Groups["op1"].Value;
                    double operand = double.Parse(value);
                    double result = Math.Log(operand);

                    return result.ToString("F5");
                });
                expression = Regex.Replace(expression, sqrtPattern, delegate(Match match)//Find, calculate and replace sqrt(decimal) 
                {
                    string value = match.Groups["op1"].Value;
                    double operand = double.Parse(value);
                    double result = Math.Sqrt(operand);

                    return result.ToString("F5");
                });
                expression = Regex.Replace(expression, powPattern, delegate(Match match)//Find, calculate and replace pow(decimal,decimal);
                {
                    string op1 = match.Groups["op1"].Value;
                    string op2 = match.Groups["op2"].Value;
                    double operand1 = double.Parse(op1);
                    double operand2 = double.Parse(op2);

                    double result = Math.Pow(operand1, operand2);

                    return result.ToString("F5");
                });
            }

            Queue<object> rpn = ShuntingYard(expression);//http://en.wikipedia.org/wiki/Shunting-yard_algorithm
            decimal mathResult = CalcReversePolishNotation(rpn);//http://en.wikipedia.org/wiki/Reverse_Polish_notation
            Console.WriteLine(mathResult);


        }

        private static decimal CalcReversePolishNotation(Queue<object> rpn)
        {
            Stack<decimal> operands = new Stack<decimal>();
            decimal operand1;
            decimal operand2;
            decimal currentRes;

            while (rpn.Count>0)
            {
                object current = rpn.Dequeue();
                if (current is decimal)
                {
                    operands.Push((decimal)current);
                }
                else
                {
                    operand2 = operands.Pop();
                    operand1 = operands.Pop();
                    currentRes = CalcCurrent(current.ToString(),operand1,operand2);
                    operands.Push(currentRes);
                }
            }
            return operands.Pop();
        }

        private static decimal CalcCurrent(string current, decimal operand1, decimal operand2)
        {
            switch (current)
            {
                case "+": return operand1 + operand2;
                case "-": return operand1 - operand2;
                case "*": return operand1 * operand2;
                case "/": return operand1 / operand2;
                default: return 0;
            }
        }

        private static Queue<object> ShuntingYard(string expression)
        {
            Queue<object> output = new Queue<object>();
            Stack<char> operators = new Stack<char>();
            StringBuilder currentNum = new StringBuilder();

            Dictionary<char, Op> operatorsPrecedence = new Dictionary<char, Op>();
            operatorsPrecedence.Add('-', new Op(2,'-',true));
            operatorsPrecedence.Add('+', new Op(2,'+',true));
            operatorsPrecedence.Add('/', new Op(3,'/',true));
            operatorsPrecedence.Add('*', new Op(3,'*',true));

            int i = 0;

            while (i < expression.Length)
            {
                char symbol = expression[i];
                if (IsDigitOrDot(symbol))//Extract a decimal -> 3.12
                {
                    currentNum.Append(symbol);
                    i++;
                    if (i == expression.Length)
                    {
                        output.Enqueue(decimal.Parse(currentNum.ToString()));
                        currentNum.Clear();
                    }
                    continue;
                }
                else if (currentNum.Length > 0)
                {
                    output.Enqueue(decimal.Parse(currentNum.ToString()));
                    currentNum.Clear();
                }

                if (symbol != '(' && symbol != ')' && operators.Count > 0 && operators.Peek() != '(' && operators.Count > 0 &&
                    ((operatorsPrecedence[operators.Peek()].Precedence >= operatorsPrecedence[symbol].Precedence && operatorsPrecedence[symbol].IsLeftAssociative) ||
                     (operatorsPrecedence[operators.Peek()].Precedence > operatorsPrecedence[symbol].Precedence)))
                {
                    output.Enqueue(operators.Pop());
                }
                else if (symbol == ')')
                {
                    while (operators.Peek() != '(')
                    {
                        output.Enqueue(operators.Pop());
                    }
                    operators.Pop();
                }

                if (symbol != ')')
                {
                    operators.Push(symbol);
                }
                i++;
            }

            while (operators.Count > 0)
            {
                output.Enqueue(operators.Pop());
            }
            
            return output;
        }

        private static bool IsDigitOrDot(char p)
        {
            if (p == '.' || char.IsDigit(p))
            {
                return true;
            }

            return false;
        }
    }

    class Op
    {
        public int Precedence { get; set; }

        public char Operator { get; set; }

        public bool IsLeftAssociative { get; set; }
        
        public Op(int precedence, char @operator, bool isLeftAssociative)
        {
            this.Precedence = precedence;
            this.Operator = @operator;
            this.IsLeftAssociative = isLeftAssociative;
        }
    }
}